﻿#region

using System;
using System.ComponentModel;
using System.Diagnostics.Contracts;
using System.Reflection;
using System.Threading;

#endregion

namespace SharpObservation
{
    ///<summary>
    ///  Extends EventHandler with methods that convert the delegates to hold weak references to their target instances.
    ///</summary>
    ///<remarks>
    ///  A common problem with the standard CLR event model is that events cause a reverse reference to exist whereby the "observed object" 
    ///  (i.e. the instance raising the event) implicitly references the observing object (the instance that contains the method invoked by the delegate).
    ///  DelegateExtensions extends EventHandler, PropertyChangedEventHandler and PropertyChangingEventHandler with the AsWeak and AsWeakAsync methods.
    ///  These methods provide a resonably performant solution to the aforementioned reverse reference anti-pattern, by converting a delegate into a 
    ///  "weak reference" delegate which does not keep the observer alive.
    ///
    ///  AsWeak creates a weak reference delegate which (optionally) attempts to invoke the tagrte method using a SynchronizationContext.
    ///  AsWeakAsync creates a weak reference delegate which invokes the target method asynchronously of the thread which raised the event - either by using 
    ///  ThreadPool.QueueWorkUserItem, or by using SynchronizationContext.Post if a synchronization context is available.
    /// 
    ///  The underlying implementation of constructs instances of WeakEventHandler or ContextWeakEventHandler in order to correctly manage the weak reference
    ///  and resolve synchronization.
    /// 
    ///  While every effort has been made to make this implementation performant (and it is indeed far more performant than the .NET WeakEventManager pattern),
    ///  it can still be [marginally] out-performed by a well-written Dispose pattern on the part of the observer.
    /// 
    ///  A more performant solution to the weak referencing event pattern is for a class to simply 
    ///  implement a proxy class (derived from WeakReference) which exposes the required callback methods, 
    ///  and have those methods in tern cass control on to the target instance.
    ///</remarks>
    public static class DelegateExtensions
    {
        #region Private Static Helpers

        private const BindingFlags MyBindingFlags = BindingFlags.NonPublic | BindingFlags.Static;

        private static readonly MethodInfo CreateWeakEventHandlerMethod
            = typeof(DelegateExtensions).GetMethod(nameof(CreateWeakEventHandler), MyBindingFlags);

        private static readonly MethodInfo CreateWeakPropertyChangedMethod
            = typeof(DelegateExtensions).GetMethod(nameof(CreateWeakPropertyChangedEventHandler), MyBindingFlags);

        private static readonly MethodInfo CreateWeakPropertyChangingMethod
            = typeof(DelegateExtensions).GetMethod(nameof(CreateWeakPropertyChangingEventHandler), MyBindingFlags);


        /// <summary>
        ///   Implementation of the Weak Reference delegate factory
        /// </summary>
        private static EventHandler<TEvent> CreateWeakEventHandler<TListener, TEvent>(
            EventHandler<TEvent> eventHandler, TListener listener,
            Action<object, EventHandler<TEvent>> removeHandler)
            where TEvent : EventArgs
            where TListener : class
        {
            Contract.Requires(listener != null);
            Contract.Requires(eventHandler != null);
            Contract.Requires(typeof(TListener) == eventHandler.Target.GetType());

            var weakHandler = new WeakEventHandler<TListener, TEvent>(listener, eventHandler.Method, removeHandler);
            return weakHandler.Invoke;
        }

        /// <summary>
        ///   Implementation of the Weak Reference delegate factory
        /// </summary>
        private static PropertyChangedEventHandler CreateWeakPropertyChangedEventHandler<TListener>(
            PropertyChangedEventHandler eventHandler, Action<object, PropertyChangedEventHandler> removeHandler)
            where TListener : class
        {
            Contract.Requires(eventHandler != null);
            Contract.Requires(typeof(TListener) == eventHandler.Target.GetType());
            var weakHandler = new WeakEventHandler<TListener, PropertyChangedEventArgs>(
                eventHandler.Target as TListener, eventHandler.Method);
            var handler = (PropertyChangedEventHandler)weakHandler.Invoke;
            weakHandler.RemoveHandler = (o, e) => removeHandler(o, handler);
            return handler;
        }

        /// <summary>
        ///   Implementation of the Weak Reference delegate factory
        /// </summary>
        private static PropertyChangingEventHandler CreateWeakPropertyChangingEventHandler<TListener>(
            PropertyChangingEventHandler eventHandler, Action<object, PropertyChangingEventHandler> removeHandler)
            where TListener : class
        {
            Contract.Requires(eventHandler != null);
            Contract.Requires(typeof(TListener) == eventHandler.Target.GetType());
            var weakHandler =
                new WeakEventHandler<TListener, PropertyChangingEventArgs>(eventHandler.Target as TListener,
                                                                           eventHandler.Method);
            var handler = (PropertyChangingEventHandler)weakHandler.Invoke;
            weakHandler.RemoveHandler = (o, e) => removeHandler(o, handler);
            return handler;
        }

        /// <summary>
        ///   Default implementation for func to take when the target of a _property change notification has been destroyed
        /// </summary>
        /// <param name = "sender"></param>
        /// <param name = "handler"></param>
        private static void RemovePropertyChangedEventHandler(object sender, PropertyChangedEventHandler handler)
        {
            var notifier = sender as INotifyPropertyChanged;
            if (notifier != null)
                notifier.PropertyChanged -= handler;
        }

        /// <summary>
        ///   Default implementation for func to take when the target of a _property change notification has been destroyed
        /// </summary>
        /// <param name = "sender"></param>
        /// <param name = "handler"></param>
        private static void RemovePropertyChangingEventHandler(object sender, PropertyChangingEventHandler handler)
        {
            var notifier = sender as INotifyPropertyChanging;
            if (notifier != null)
                notifier.PropertyChanging -= handler;
        }

        #endregion

        #region EventHandler<>.AsWeak

        /// <summary>
        ///   Returns a delegate which keeps a weak reference to it's target site.
        /// </summary>
        /// <typeparam name = "TEvent">The event callback typeToMatch to be wrapped</typeparam>
        /// <param name = "eventHandler">The event callback to be wrapped</param>
        /// <returns>A call-compatible delegate which does not hard-reference the target site</returns>
        /// <remarks>
        ///   By default, delegate retain a hard reference to their target instance, which create the scenario where
        ///   UI objects are kept alive by the objects they are observing. While WPF remedies this with the WeakReferenceManager pattern,
        ///   this extension method offers a far cleaner, generic alternative.
        ///   The underlying implementation of AsWeak automatically determines whether the calling thread is linked to a SynchornizationContext.
        ///   If so, the SynchornizationContext is used to invoke the delegate on the _callerType's thread. If no SynchornizationContext exists, then the
        ///   delegate is invopked on the same thread that raises the event.
        /// </remarks>
        public static EventHandler<TEvent> AsWeak<TEvent>(this EventHandler<TEvent> eventHandler)
            where TEvent : EventArgs
        {
            return AsWeak(eventHandler, null);
        }


        /// <summary>
        ///   Returns a delegate which keeps a weak reference to it's target site.
        /// </summary>
        /// <typeparam name = "TEvent">The event callback typeToMatch to be wrapped</typeparam>
        /// <param name = "eventHandler">The event callback to be wrapped</param>
        /// <param name = "removeHandler">The func for removing the delegate from an event handler if the listener has been garbage collected.</param>
        /// <returns>A call-compatible delegate which does not hard-reference the target site</returns>
        /// <remarks>
        ///   By default, delegate retain a hard reference to their target instance, which create the scenario where
        ///   UI objects are kept alive by the objects they are observing. While WPF remedies this with the WeakReferenceManager pattern,
        ///   this extension method offers a far cleaner, generic alternative.
        ///   The underlying implementation of AsWeak automatically determines whether the calling thread is linked to a SynchornizationContext.
        ///   If so, the SynchornizationContext is used to invoke the delegate on the _callerType's thread. If no SynchornizationContext exists, then the
        ///   delegate is invopked on the same thread that raises the event.
        /// </remarks>
        public static EventHandler<TEvent> AsWeak<TEvent>(this EventHandler<TEvent> eventHandler,
                                                          Action<object, EventHandler<TEvent>> removeHandler)
            where TEvent : EventArgs
        {
            Contract.Requires(eventHandler != null);
            Contract.Requires(eventHandler.Target != null);
            var target = eventHandler.Target;
            var generic =
                CreateWeakEventHandlerMethod.MakeGenericMethod(new[] { target.GetType(), typeof(TEvent) });
            return (EventHandler<TEvent>)generic.Invoke(null, new[] { eventHandler, target, removeHandler });
        }

        /// <summary>
        ///   Returns a delegate which keeps a weak reference to it's target site.
        /// </summary>
        /// <typeparam name = "TEvent">The event callback typeToMatch to be wrapped</typeparam>
        /// <typeparam name = "TListener">The type of listener</typeparam>
        /// <param name = "eventHandler">The event callback to be wrapped</param>
        /// <param name = "listener">A reference to the instance containing the method to invoke</param>
        /// <param name = "removeHandler">The func for removing the delegate from an event handler if the listener has been garbage collected.</param>
        /// <returns>A call-compatible delegate which does not hard-reference the target site</returns>
        /// <remarks>
        ///   This method performs slightly faster than the parameterless AsWeak extension, because the TListener typeToMatch reference 
        ///   allows the method to bypass the reflection API and instantiate a wrapper directly, at the expense of less hygienic calling code.
        /// </remarks>
        public static EventHandler<TEvent> AsWeak<TListener, TEvent>(
            this EventHandler<TEvent> eventHandler,
            TListener listener,
            Action<object, EventHandler<TEvent>> removeHandler)
            where TEvent : EventArgs
            where TListener : class
        {
            Contract.Requires(listener != null);
            Contract.Requires(eventHandler != null);
            Contract.Requires(typeof(TListener) == eventHandler.Target.GetType());

            return CreateWeakEventHandler(eventHandler, listener, removeHandler);
        }

        #endregion

        #region EventHandler<>.AsAsync

        /// <summary>
        ///  Wraps the eventHandler so that it is invoked asynchronously, and on the caller's thread if a SynchronizationContext is available.
        /// </summary>
        /// <typeparam name = "TEvent">The typeToMatch of event arg for the EventHandler</typeparam>
        /// <param name = "eventHandler">The callback to be wrapped</param>
        /// <returns>A delegate wrapper which invokes inner delegate asynchronously</returns>
        public static EventHandler<TEvent> AsAsync<TEvent>(this EventHandler<TEvent> eventHandler)
            where TEvent : EventArgs
        {
            return AsAsync(eventHandler, SynchronizationContext.Current);
        }

        /// <summary>
        ///   Wraps the eventHandler so that it is invoked asynchronously, and on the caller's thread if a SynchronizationContext is available.
        /// </summary>
        /// <typeparam name = "TEvent">The typeToMatch of event arg for the EventHandler</typeparam>
        /// <param name = "eventHandler">The callback to be wrapped</param>
        /// <param name = "syncContext">Optional. The context in which the callback is posted</param>
        /// <returns>A delegate wrapper which invokes inner delegate asynchronously</returns>
        public static EventHandler<TEvent> AsAsync<TEvent>(this EventHandler<TEvent> eventHandler,
                                                           SynchronizationContext syncContext)
            where TEvent : EventArgs
        {
            if (syncContext != null)
            {
                return (o, e) =>
                           {
                               var sopcb = new SendOrPostCallback(b => eventHandler(b, e));
                               syncContext.Post(sopcb, o);
                           };
            }
            return (o, e) =>
                       {
                           ThreadPool.QueueUserWorkItem(b => eventHandler(b, e), o);
                       };
        }

        #endregion

        #region EventHandler<>.AsSend

        /// <summary>
        ///   Wraps the eventHandler so that it is invoked synchronously on the _callerType's thread if a SynchronizationContext is available.
        /// </summary>
        /// <typeparam name = "TEvent">The typeToMatch of event arg for the EventHandler</typeparam>
        /// <param name = "eventHandler">The callback to be wrapped</param>
        /// <returns>A delegate wrapper which invokes inner delegate synchronously</returns>
        public static EventHandler<TEvent> AsSend<TEvent>(this EventHandler<TEvent> eventHandler)
            where TEvent : EventArgs
        {
            return AsSend(eventHandler, SynchronizationContext.Current);
        }

        /// <summary>
        ///   Wraps the eventHandler so that it is invoked synchronously on the _callerType's thread if a SynchronizationContext is available.
        /// </summary>
        /// <typeparam name = "TEvent">The typeToMatch of event arg for the EventHandler</typeparam>
        /// <param name = "eventHandler">The callback to be wrapped</param>
        /// <param name = "syncContext">Optional. The context in which the callback is sent</param>
        /// <returns>A delegate wrapper which invokes inner delegate synchronously</returns>
        public static EventHandler<TEvent> AsSend<TEvent>(this EventHandler<TEvent> eventHandler,
                                                          SynchronizationContext syncContext)
            where TEvent : EventArgs
        {
            if (syncContext != null)
                return (o, e) =>
                {
                    syncContext.Send(b => eventHandler(b, e), o);
                };
            return eventHandler;
        }

        #endregion

        #region PropertyChangingEventHandler.AsWeak

        /// <summary>
        ///   Returns a delegate which keeps a weak reference to it's target site.
        /// </summary>
        /// <param name = "eventHandler">The event callback to be wrapped</param>
        /// <returns>A call-compatible delegate which does not hard-reference the target site</returns>
        /// <remarks>
        ///   By default, delegate retain a hard reference to their target instance, which create the scenario where
        ///   UI objects are kept alive by the objects they are observing. While WPF remedies this with the WeakReferenceManager pattern,
        ///   this extension method offers a far cleaner, generic alternative.
        ///   The underlying implementation of AsWeak automatically determines whether the calling thread is linked to a SynchornizationContext.
        ///   If so, the SynchornizationContext is used to invoke the delegate on the _callerType's thread. If no SynchornizationContext exists, then the
        ///   delegate is invopked on the same thread that raises the event.
        /// </remarks>
        public static PropertyChangingEventHandler AsWeak(this PropertyChangingEventHandler eventHandler)
        {
            return AsWeak(eventHandler, RemovePropertyChangingEventHandler);
        }

        /// <summary>
        ///   Returns a delegate which keeps a weak reference to it's target site.
        /// </summary>
        /// <param name = "eventHandler">The event callback to be wrapped</param>
        /// <param name = "removeHandler"></param>
        /// <returns>A call-compatible delegate which does not hard-reference the target site</returns>
        /// <remarks>
        ///   By default, delegate retain a hard reference to their target instance, which create the scenario where
        ///   UI objects are kept alive by the objects they are observing. While WPF remedies this with the WeakReferenceManager pattern,
        ///   this extension method offers a far cleaner, generic alternative.
        ///   The underlying implementation of AsWeak automatically determines whether the calling thread is linked to a SynchornizationContext.
        ///   If so, the SynchornizationContext is used to invoke the delegate on the _callerType's thread. If no SynchornizationContext exists, then the
        ///   delegate is invopked on the same thread that raises the event.
        /// </remarks>
        public static PropertyChangingEventHandler AsWeak(this PropertyChangingEventHandler eventHandler,
                                                          Action<object, PropertyChangingEventHandler> removeHandler)
        {
            Contract.Requires(eventHandler != null);
            Contract.Requires(eventHandler.Target != null);
            var generic = CreateWeakPropertyChangingMethod.MakeGenericMethod(eventHandler.Target.GetType());
            return (PropertyChangingEventHandler)generic.Invoke(null, new object[] { eventHandler, removeHandler });
        }

        /// <summary>
        ///   Returns a delegate which keeps a weak reference to it's target site.
        /// </summary>
        /// <typeparam name = "TListener">The type of listener</typeparam>
        /// <param name = "eventHandler">The event callback to be wrapped</param>
        /// <returns>A call-compatible delegate which does not hard-reference the target site</returns>
        /// <remarks>
        ///   This method performs slightly faster than the parameterless AsWeak extension, because the TListener typeToMatch reference 
        ///   allows the method to bypass the reflection API and instantiate a wrapper directly, at the expense of less hygienic calling code.
        /// </remarks>
        public static PropertyChangingEventHandler AsWeak<TListener>(this PropertyChangingEventHandler eventHandler)
            where TListener : class
        {
            Contract.Requires(eventHandler != null);
            Contract.Requires(typeof(TListener) == eventHandler.Target.GetType());
            return CreateWeakPropertyChangingEventHandler<TListener>(eventHandler, RemovePropertyChangingEventHandler);
        }

        /// <summary>
        ///   Returns a delegate which keeps a weak reference to it's target site.
        /// </summary>
        /// <typeparam name = "TListener">The type of listener</typeparam>
        /// <param name = "eventHandler">The event callback to be wrapped</param>
        /// <param name = "removeHandler">The delegate to invoke when the target is not available</param>
        /// <returns>A call-compatible delegate which does not hard-reference the target site</returns>
        /// <remarks>
        ///   This method performs slightly faster than the parameterless AsWeak extension, because the TListener typeToMatch reference 
        ///   allows the method to bypass the reflection API and instantiate a wrapper directly, at the expense of less hygienic calling code.
        /// </remarks>
        public static PropertyChangingEventHandler AsWeak<TListener>(
            this PropertyChangingEventHandler eventHandler,
            Action<object, PropertyChangingEventHandler> removeHandler)
            where TListener : class
        {
            Contract.Requires(eventHandler != null);
            Contract.Requires(typeof(TListener) == eventHandler.Target.GetType());
            return CreateWeakPropertyChangingEventHandler<TListener>(eventHandler, removeHandler);
        }

        #endregion

        #region PropertyChangingEventHandler.AsAsync

        /// <summary>
        ///   Wraps the PropertyChangingEventHandler so that it is invoked asynchronously, and on the _callerType's thread if a SynchronizationContext is available.
        /// </summary>
        /// <param name = "eventHandler">The callback to be wrapped</param>
        /// <returns>A delegate wrapper which invokes inner delegate asynchronously</returns>
        public static PropertyChangingEventHandler AsAsync(this PropertyChangingEventHandler eventHandler)
        {
            return AsAsync(eventHandler, SynchronizationContext.Current);
        }

        /// <summary>
        ///   Wraps the PropertyChangingEventHandler so that it is invoked asynchronously, and on the _callerType's thread if a SynchronizationContext is available.
        /// </summary>
        /// <param name = "eventHandler">The callback to be wrapped</param>
        /// <param name = "syncContext">Optional. The context in which the callback is posted</param>
        /// <returns>A delegate wrapper which invokes inner delegate asynchronously</returns>
        public static PropertyChangingEventHandler AsAsync(this PropertyChangingEventHandler eventHandler,
                                                           SynchronizationContext syncContext)
        {
            if (syncContext != null)
            {
                return (o, e) =>
                           {
                               var sopcb = new SendOrPostCallback(b => eventHandler(b, e));
                               syncContext.Post(sopcb, o);
                           };
            }
            return (o, e) =>
                       {
                           var sopcb = new WaitCallback(b => eventHandler(b, e));
                           ThreadPool.QueueUserWorkItem(sopcb, o);
                       };
        }

        #endregion

        #region PropertyChangingEventHandler.AsSend

        /// <summary>
        ///   Wraps the PropertyChangingEventHandler so that it is invoked synchronously on the _callerType's thread if a SynchronizationContext is available.
        /// </summary>
        /// <param name = "eventHandler">The callback to be wrapped</param>
        /// <returns>A delegate wrapper which invokes inner delegate synchronously</returns>
        public static PropertyChangingEventHandler AsSend(this PropertyChangingEventHandler eventHandler)
        {
            return AsSend(eventHandler, SynchronizationContext.Current);
        }

        /// <summary>
        ///   Wraps the PropertyChangingEventHandler so that it is invoked synchronously on the _callerType's thread if a SynchronizationContext is available.
        /// </summary>
        /// <param name = "eventHandler">The callback to be wrapped</param>
        /// <param name = "syncContext">Optional. The context in which the callback is sent</param>
        /// <returns>A delegate wrapper which invokes inner delegate synchronously</returns>
        public static PropertyChangingEventHandler AsSend(this PropertyChangingEventHandler eventHandler,
                                                          SynchronizationContext syncContext)
        {
            if (syncContext != null)
            {
                return (o, e) =>
                           {
                               var sopcb = new SendOrPostCallback(b => eventHandler(b, e));
                               syncContext.Send(sopcb, o);
                           };
            }
            return eventHandler;
        }

        #endregion

        #region PropertyChangedEventHandler.AsWeak

        /// <summary>
        ///   Returns a delegate which keeps a weak reference to it's target site.
        /// </summary>
        /// <param name = "eventHandler">The event callback to be wrapped</param>
        /// <returns>A call-compatible delegate which does not hard-reference the target site</returns>
        /// <remarks>
        ///   By default, delegate retain a hard reference to their target instance, which create the scenario where
        ///   UI objects are kept alive by the objects they are observing. While WPF remedies this with the WeakReferenceManager pattern,
        ///   this extension method offers a far cleaner, generic alternative.
        ///   The underlying implementation of AsWeak automatically determines whether the calling thread is linked to a SynchornizationContext.
        ///   If so, the SynchornizationContext is used to invoke the delegate on the _callerType's thread. If no SynchornizationContext exists, then the
        ///   delegate is invopked on the same thread that raises the event.
        /// </remarks>
        public static PropertyChangedEventHandler AsWeak(this PropertyChangedEventHandler eventHandler)
        {
            Contract.Assert(eventHandler == null);

            return AsWeak(eventHandler, RemovePropertyChangedEventHandler);
        }

        /// <summary>
        ///   Returns a delegate which keeps a weak reference to it's target site.
        /// </summary>
        /// <param name = "eventHandler">The event callback to be wrapped</param>
        /// <param name = "removeHandler"></param>
        /// <returns>A call-compatible delegate which does not hard-reference the target site</returns>
        /// <remarks>
        ///   By default, delegate retain a hard reference to their target instance, which create the scenario where
        ///   UI objects are kept alive by the objects they are observing. While WPF remedies this with the WeakReferenceManager pattern,
        ///   this extension method offers a far cleaner, generic alternative.
        ///   The underlying implementation of AsWeak automatically determines whether the calling thread is linked to a SynchornizationContext.
        ///   If so, the SynchornizationContext is used to invoke the delegate on the _callerType's thread. If no SynchornizationContext exists, then the
        ///   delegate is invopked on the same thread that raises the event.
        /// </remarks>
        public static PropertyChangedEventHandler AsWeak(this PropertyChangedEventHandler eventHandler,
                                                         Action<object, PropertyChangedEventHandler> removeHandler)
        {
            Contract.Assert(eventHandler == null);
            Contract.Assert(removeHandler == null);

            Contract.Requires(eventHandler != null);
            Contract.Requires(eventHandler.Target != null);
            var generic = CreateWeakPropertyChangedMethod.MakeGenericMethod(eventHandler.Target.GetType());
            return (PropertyChangedEventHandler)generic.Invoke(null, new object[] { eventHandler, removeHandler });
        }

        /// <summary>
        ///   Returns a delegate which keeps a weak reference to it's target site.
        /// </summary>
        /// <param name = "eventHandler">The event callback to be wrapped</param>
        /// <returns>A call-compatible delegate which does not hard-reference the target site</returns>
        /// <remarks>
        ///   This method performs slightly faster than the parameterless AsWeak extension, because the TListener typeToMatch reference 
        ///   allows the method to bypass the reflection API and instantiate a wrapper directly, at the expense of less hygienic calling code.
        /// </remarks>
        public static PropertyChangedEventHandler AsWeak<TListener>(this PropertyChangedEventHandler eventHandler)
            where TListener : class
        {
            Contract.Assert(eventHandler == null);

            Contract.Requires(eventHandler != null);
            Contract.Requires(typeof(TListener) == eventHandler.Target.GetType());
            return CreateWeakPropertyChangedEventHandler<TListener>(eventHandler, RemovePropertyChangedEventHandler);
        }

        #endregion

        #region PropertyChangedEventHandler.AsAsync

        /// <summary>
        ///   Wraps the PropertyChangedEventHandler so that it is invoked asynchronously, and on the _callerType's thread if a SynchronizationContext is available.
        /// </summary>
        /// <param name = "eventHandler">The callback to be wrapped</param>
        /// <returns>A delegate wrapper which invokes inner delegate asynchronously</returns>
        public static PropertyChangedEventHandler AsAsync(this PropertyChangedEventHandler eventHandler)
        {
            return AsAsync(eventHandler, SynchronizationContext.Current);
        }

        /// <summary>
        ///   Wraps the PropertyChangedEventHandler so that it is invoked asynchronously, and on the _callerType's thread if a SynchronizationContext is available.
        /// </summary>
        /// <param name = "eventHandler">The callback to be wrapped</param>
        /// <param name = "syncContext">Optional. The context in which the callback is posted</param>
        /// <returns>A delegate wrapper which invokes inner delegate asynchronously</returns>
        public static PropertyChangedEventHandler AsAsync(this PropertyChangedEventHandler eventHandler,
                                                          SynchronizationContext syncContext)
        {
            if (syncContext != null)
            {
                return (o, e) =>
                           {
                               var sopcb = new SendOrPostCallback(b => eventHandler(b, e));
                               syncContext.Post(sopcb, o);
                           };
            }
            return (o, e) =>
                       {
                           var sopcb = new WaitCallback(b => eventHandler(b, e));
                           ThreadPool.QueueUserWorkItem(sopcb, o);
                       };
        }

        #endregion

        #region PropertyChangedEventHandler.AsSend

        /// <summary>
        ///  Wraps the PropertyChangedEventHandler so that it is invoked synchronously on the nameof(eventHandler) if a SynchronizationContext is available.
        /// </summary>
        /// <param name = "eventHandler">The callback to be wrapped</param>
        /// <returns>A delegate wrapper which invokes inner delegate synchronously</returns>
        public static PropertyChangedEventHandler AsSend(this PropertyChangedEventHandler eventHandler)
        {
            return AsSend(eventHandler, SynchronizationContext.Current);
        }

        /// <summary>
        ///   Wraps the PropertyChangedEventHandler so that it is invoked synchronously on the _callerType's thread if a SynchronizationContext is available.
        /// </summary>
        /// <param name = "eventHandler">The callback to be wrapped</param>
        /// <param name = "syncContext">Optional. The context in which the callback is sent</param>
        /// <returns>A delegate wrapper which invokes inner delegate synchronously</returns>
        public static PropertyChangedEventHandler AsSend(this PropertyChangedEventHandler eventHandler,
                                                         SynchronizationContext syncContext)
        {
            if (syncContext != null)
            {
                return (o, e) =>
                           {
                               var sopcb = new SendOrPostCallback(b => eventHandler(b, e));
                               syncContext.Send(sopcb, o);
                           };
            }
            return eventHandler;
        }

        #endregion

        #region Nested type: WeakEventHandler

        /// <summary>
        ///   Implements a weak-referencing delegate that is call-compatible with EventHandler
        /// </summary>
        /// <typeparam name = "TListener">The typeToMatch of listener (target) for the event</typeparam>
        /// <typeparam name = "TEvent">The typeToMatch of EventArg of the event handler</typeparam>
        private sealed class WeakEventHandler<TListener, TEvent> : WeakReference<TListener>
            where TEvent : EventArgs
            where TListener : class
        {
            private readonly OpenEventHandler _fireEvent;

            internal Action<object, EventHandler<TEvent>> RemoveHandler;

            internal WeakEventHandler(TListener target, MethodInfo method)
                : base(target)
            {
                _fireEvent = (OpenEventHandler)Delegate.CreateDelegate(typeof(OpenEventHandler), null, method);
            }

            internal WeakEventHandler(TListener target, MethodInfo method,
                                      Action<object, EventHandler<TEvent>> removeHandler)
                : base(target)
            {
                _fireEvent = (OpenEventHandler)Delegate.CreateDelegate(typeof(OpenEventHandler), null, method);
                RemoveHandler = removeHandler;
            }

            internal void Invoke(object sender, TEvent e)
            {
                var target = Target;

                if (target == null)
                {
                    if (RemoveHandler != null)
                        RemoveHandler(sender, Invoke);
                }
                else
                {
                    var disposable = target as IIsDisposed;
                    if (disposable != null && disposable.IsDisposed)
                    {
                        Target = null;
                        if (RemoveHandler != null)
                            RemoveHandler(sender, Invoke);
                    }
                    else
                    {
                        _fireEvent(target, sender, e);
                    }
                }
            }

            #region Nested type: OpenEventHandler

            private delegate void OpenEventHandler(TListener @this, object sender, TEvent e);

            #endregion
        }

        #endregion
    }
}